﻿// Copyright (c) 2013 fancidev
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

// This class is depreciated in favor of PixelBuffer.
#if false
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Util.Media
{
    /// <summary>
    /// Encapsulates the pixel data of an image as a Stream of bytes.
    /// </summary>
    /// <remarks>
    /// This base class adds a new abstract property, PixelFormat, which
    /// returns the pixel format of the underlying stream. This is useful
    /// to determine e.g. the number of bytes per pixel.
    /// 
    /// In addition, this base class provides default implementations for
    /// several members of the Stream interface to simplify the work of 
    /// derived classes for typical usage scenarios. Typically a pixel stream
    /// is backed by a fixed-size buffer, therefore its characteristics is 
    /// similar to that of a memory stream: it is typically readable, 
    /// writable, and seekable, but its length cannot change. 
    /// </remarks>
    public abstract class PixelStream : Stream
    {
        /// <summary>
        /// An internal position indicator. The default implementation uses
        /// this field to keep track of the current position of the stream.
        /// </summary>
        private long position = 0;

        /// <summary>
        /// Gets the pixel format of the image.
        /// </summary>
        public abstract PixelFormat PixelFormat { get; }

        /// <summary>
        /// The default implementation always returns true.
        /// </summary>
        public override bool CanRead { get { return true; } }

        /// <summary>
        /// The default implementation always returns true.
        /// </summary>
        public override bool CanWrite { get { return true; } }

        /// <summary>
        /// The default implementation always returns true.
        /// </summary>
        public override bool CanSeek { get { return true; } }

        // The derived class must override this to return the length of the
        // stream in bytes.
        // public override long Length { get; }

        /// <summary>
        /// The default implementation sets the Position property to the
        /// requested position.
        /// </summary>
        public override long Seek(long offset, SeekOrigin origin)
        {
            long pos;
            switch (origin)
            {
                case SeekOrigin.Begin:
                    pos = 0;
                    break;
                case SeekOrigin.End:
                    pos = this.Length;
                    break;
                case SeekOrigin.Current:
                    pos = this.Position;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("origin");
            }
            pos += offset;
            if (pos < 0 || pos > this.Length)
                throw new ArgumentOutOfRangeException("offset");

            this.Position = pos;
            return this.Position;
        }

        /// <summary>
        /// The default implementation uses an internal field to keep track
        /// of the current stream position. When setting the position, the
        /// value must be between 0 and Length, inclusive.
        /// </summary>
        public override long Position
        {
            get { return this.position; }
            set
            {
                if (value < 0 || value > this.Length)
                    throw new ArgumentOutOfRangeException("value");
                this.position = value;
            }
        }

        /// <summary>
        /// The default implementation throws NotSupportedException.
        /// </summary>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// The base implementation verifies the arguments, and then calls
        /// ReadCore() with arguments guaranteed to satisfy the following
        /// conditions:
        /// 
        ///   1. input arguments buffer, offset, count are valid.
        ///   2. count > 0 and Position + count &le; Length.
        ///   
        /// The derived class must implement ReadCore() unless it overrides
        /// Read(). It must return the number of bytes actually read, which
        /// MUST be positive but may be less than the number of bytes 
        /// requested. 
        /// 
        /// The derived class MUST NOT set the Position property. The internal
        /// stream position is automatically updated using the return value
        /// from ReadCore().
        /// </summary>
        public override int Read(byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0 || offset + count > buffer.Length)
                throw new ArgumentOutOfRangeException("count");

            count = (int)Math.Min((long)count, this.Length - this.Position);
            if (count == 0)
                return 0;

            int actual = ReadCore(buffer, offset, count);
            if (actual <= 0)
                throw new ArgumentException("ReadCore() must return a positive value.");
            if (actual > count)
                throw new ArgumentException("ReadCore() returned a value past the end of the stream.");
            this.position += actual;
            return actual;
        }

        /// <summary>
        /// See Read() for details.
        /// </summary>
        protected virtual int ReadCore(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// The base implementation verifies the arguments, and then calls
        /// WriteCore() repeatly with arguments guaranteed to satisfy the 
        /// following conditions:
        /// 
        ///   1. input arguments buffer, offset, count are valid.
        ///   2. count > 0 and Position + count &le; Length.
        ///   
        /// The derived class must implement WriteCore() unless it overrides
        /// Write(). It must return the number of bytes actually written, 
        /// which MUST be positive but may be less than the number of bytes 
        /// requested. 
        /// 
        /// The derived class MUST NOT set the Position property. The internal
        /// stream position is automatically updated using the return value
        /// from WriteCore().
        /// </summary>
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0 || offset + count > buffer.Length)
                throw new ArgumentOutOfRangeException("count");
            if (this.Position + count > this.Length)
                throw new EndOfStreamException();

            while (count > 0)
            {
                int actual = WriteCore(buffer, offset, count);
                if (actual <= 0)
                    throw new ArgumentException("WriteCore() must return a positive value.");
                if (actual > count)
                    throw new ArgumentException("WriteCore() returned a value past the end of the stream.");

                this.Position += actual;
                offset += actual;
                count -= actual;
            }
        }

        /// <summary>
        /// See Write() for details.
        /// </summary>
        protected virtual int WriteCore(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// The default implementation does nothing.
        /// </summary>
        public override void Flush() { }
    }

    /// <summary>
    /// Encapsulates the pixel data of a GDI Bitmap as a Stream of bytes.
    /// 
    /// Each pixel is represented by a consecutive number of bits (usually in
    /// an integral number of bytes). The pixels are accessed scanline by 
    /// scanline from top to bottom, and in each scanline from left to right.
    /// No padding exists in a scanline.
    /// 
    /// Using this class is similar to accessing the underlying BitmapData
    /// directly except you don't need to worry about stride and orientation.
    /// </summary>
    public class BitmapPixelStream : PixelStream
    {
        private Bitmap bmp;
        private BitmapData bmpData;
        private ImageLockMode access;
        //private byte[] scanline;
        private int x, y;
        private IntPtr ptrScanline;
        private int bytesPerScanline;

        public BitmapPixelStream(
            Bitmap bitmap, PixelFormat pixelFormat, ImageLockMode access)
        {
            if (bitmap == null)
                throw new ArgumentNullException("bitmap");
            if (access != ImageLockMode.ReadOnly &&
                access != ImageLockMode.WriteOnly &&
                access != ImageLockMode.ReadWrite)
                throw new ArgumentOutOfRangeException("access");
            int bpp = GetBitsPerPixel(pixelFormat);
            if (bpp < 8)
                throw new NotSupportedException("Pixel format not supported.");

            this.bmp = bitmap;
            this.bmpData = bitmap.LockBits(
                new Rectangle(0, 0, bmp.Width, bmp.Height),
                access,
                pixelFormat);
            this.access = access;
            //this.scanline = new int[bmp.Width];
            this.x = 0;
            this.y = 0;
            this.ptrScanline = bmpData.Scan0;
            this.bytesPerScanline = bmp.Width * (bpp / 8);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (bmp != null)
                {
                    x = y = 0;
                    ptrScanline = IntPtr.Zero;
                    bmp.UnlockBits(bmpData);
                    bmp = null;
                }
            }
            base.Dispose(disposing);
        }

        public override PixelFormat PixelFormat
        {
            get { return bmp.PixelFormat; }
        }

        public override bool CanRead
        {
            get
            {
                return (access == ImageLockMode.ReadOnly)
                    || (access == ImageLockMode.ReadWrite);
            }
        }

        public override bool CanWrite
        {
            get
            {
                return (access == ImageLockMode.WriteOnly)
                    || (access == ImageLockMode.ReadWrite);
            }
        }

        public override bool CanSeek { get { return true; } }

        public override int Read(byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0 || offset + count > buffer.Length)
                throw new ArgumentOutOfRangeException("count");

            if (y >= bmp.Height)
                return 0;
            if (count == 0)
                return 0;

            count = Math.Min(count, bytesPerScanline - x);
            Marshal.Copy(ptrScanline + x, buffer, offset, count);
            x += count;
            if (x == bytesPerScanline)
            {
                y++;
                x = 0;
                ptrScanline += bmpData.Stride;
            }
            return count;
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0 || offset + count > buffer.Length)
                throw new ArgumentOutOfRangeException("count");
            if (this.Position + count > this.Length)
                throw new EndOfStreamException();

            // Write scanline by scanline.
            while (count > 0 && y < bmp.Height)
            {
                int bytesToWrite = Math.Min(count, bytesPerScanline - x);
                Marshal.Copy(buffer, offset, ptrScanline + x, bytesToWrite);
                x += bytesToWrite;
                if (x == bytesPerScanline)
                {
                    y++;
                    x = 0;
                    ptrScanline += bmpData.Stride;
                }
                offset += bytesToWrite;
                count -= bytesToWrite;
            }
            if (count > 0)
                throw new EndOfStreamException();
        }

        public override void Flush() { }

        public override long Seek(long offset, SeekOrigin origin)
        {
            long pos;
            switch (origin)
            {
            case SeekOrigin.Begin:
                pos = 0;
                break;
            case SeekOrigin.End:
                pos = Length;
                break;
            case SeekOrigin.Current:
                pos = Position;
                break;
            default:
                throw new ArgumentOutOfRangeException("origin");
            }
            pos += offset;
            if (pos < 0 || pos > Length)
                throw new ArgumentOutOfRangeException("offset");

            y = (int)(pos / bytesPerScanline);
            x = (int)(pos % bytesPerScanline);
            ptrScanline = bmpData.Scan0 + bmpData.Stride * y;
            return pos;
        }

        public override long Position
        {
            get { return y * bytesPerScanline + x; }
            set { Seek(value, SeekOrigin.Begin); }
        }

        public override long Length
        {
            get { return bmp.Height * bytesPerScanline; }
        }

        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        public static int GetBitsPerPixel(PixelFormat format)
        {
            switch (format)
            {
            case PixelFormat.Format1bppIndexed:
                return 1;
            case PixelFormat.Format4bppIndexed:
                return 4;
            case PixelFormat.Format8bppIndexed:
                return 8;
            case PixelFormat.Format16bppArgb1555:
            case PixelFormat.Format16bppGrayScale:
            case PixelFormat.Format16bppRgb555:
            case PixelFormat.Format16bppRgb565:
                return 16;
            case PixelFormat.Format24bppRgb:
                return 24;
            case PixelFormat.Format32bppArgb:
            case PixelFormat.Format32bppPArgb:
            case PixelFormat.Format32bppRgb:
                return 32;
            case PixelFormat.Format48bppRgb:
                return 48;
            case PixelFormat.Format64bppArgb:
            case PixelFormat.Format64bppPArgb:
                return 64;
            default:
                return 0;
            }
        }
    }

    // TODO: stride is not used... this must be wrong.
    public class ArrayPixelStream<T> : PixelStream
    {
        private T[] array;
        PixelFormat pixelFormat;
        private int elemSize;
        private int bytesPerScanline;
        private int stride;
        private int height;

        public ArrayPixelStream(T[] array, PixelFormat pixelFormat, int stride, int bytesPerScanline)
        {
            this.array = array;
            this.pixelFormat = pixelFormat;
            this.stride = stride;
            this.bytesPerScanline = bytesPerScanline;
            this.elemSize = Marshal.SizeOf(typeof(T));
            this.height = elemSize * array.Length / stride;
        }

        public override PixelFormat PixelFormat
        {
            get { return this.pixelFormat; }
        }

        public override long Length
        {
            get { return bytesPerScanline * height; }
        }

        protected override int ReadCore(byte[] buffer, int offset, int count)
        {
            int pos = (int)base.Position;
            int x = pos % bytesPerScanline;
            Buffer.BlockCopy(this.array, pos, buffer, offset, count);
            return count;
        }

        protected override int WriteCore(byte[] buffer, int offset, int count)
        {
            int pos = (int)this.Position;
            int x = pos % bytesPerScanline;
            count = Math.Min(count, bytesPerScanline - x);
            Buffer.BlockCopy(buffer, offset, this.array, pos, count);
            return count;
        }
    }
}
#endif
